
# This module defines visual elements that interact with the player

import shape

from sprites import Sprite, Target, pygame
from pygame import Color, draw, Rect
from library import Resource
from utils import rectify, Object


# Load a set of styles into memory. This styles are used to
# skin other objects of the interface hierarchy
class Style(object):
    
    # List of all style rules by class selector
    rules = dict()
    
    # Parse a block of text into rules
    @classmethod
    def parse(cls, text):
        assert '}' in text, "Invalid style definition"
        
        # Split the full text into chunks of rules
        for block in text.split('}')[:-1]:
            selector, block = block.split('{')
            selector = selector.strip().lower()
            
            # Split the rules block into individual rules
            rules = dict()
            for line in block.split(';'):
                if not line or line.isspace(): continue
                rule, actions = line.split(':')
                rule = rule.strip().lower()
                actions = actions.strip().split()
                
                # Do some preprocessing on individual rule actions
                for i, action in enumerate(actions):
                    if action.isdigit(): actions[i] = int(action)
                    elif action.startswith('file(') and action.endswith(')'):
                        actions[i] = Resource(action[5:-1])
                    elif action.startswith('path(') and action.endswith(')'):
                        actions[i] = Resource.path(action[5:-1])
                    elif action == "True": actions[i] = True
                    elif action == "None": actions[i] = None
                    elif action == "False": actions[i] = False
                    elif action.startswith('#'):
                        action = action[1:]
                        if action.isdigit(): action = int(action)
                        actions[i] = Color(action)
                
                # Special preprocessing for font definitions
                if 'font' in rule:
                    actions = pygame.font.Font(*actions)
                    
                # Ony multiple action rules stay as sequences
                elif len(actions) == 1: actions = actions[0]
                rules[rule.replace('-', '_')] = actions
                
            # Add the newly parsed rule to the Style ruleset
            cls.rules[selector] = Object(rules)
            
    # Create a new Style object from a selector
    def __new__(cls, selector):
        assert selector in cls.rules, "Style not loaded"
        return cls.rules[selector]


# Creates a roaming textbox that displays quick info
class Label(Target):
    
    group = pygame.sprite.Group()
    
    def __init__(self, text, target=None, style="label", **kwargs):
        # Iniatialize Sprite base and set translucency
        Target.__init__(self, vgroup=Label.group, **kwargs)
        
        # Fetch the specified instance style
        self.style = Style(style)

        # set initial tooltip text
        self.set_text(text)
        
        # Anchor to target
        self.target = Target.search(target)
        if isinstance(self.target, Target): 
            self.target.watch('rect', self.follow_target)

        # Correct initial tooltip position
        self.rect = rectify(self.rect, self.image.get_size())

    def set_text(self, text):
        
        # Prepare from variable beforehand
        text = text.strip(); bg = self.style.bg; font = self.style.font
        s = font.size(text); r = s[1]//2
        
        # Create the tooltip canvas
        self.image = img = pygame.Surface((s[0] + s[1], s[1])).convert_alpha()
        img.fill((0,0,0,0))
        draw.circle(img, bg, (r,r), r, 0)
        draw.circle(img, bg, (r+s[0],r), r, 0)
        
        # Blit text to canvas and set image transparency
        img.blit(font.render(text, True, self.style.fg, bg), (r,0))
        self.translucify(190)
        
        # Just for the records
        self.__text = text
        
        
    def get_text(self): return self.__text
    text = property(get_text, set_text, None)
        
    # Renders tooltip in correct spot over each iteration
    # This is done because tooltip must always visually follow its target
    def follow_target(self, attribute, rect, source):
        if rect is not None: self.align(rect, 'right,left,top,bottom')
        
    # Called when the Tooltip needs to be destroyed
    def remove(self): del self.target; self.kill()
    
        
class Dialog(Target):
    
    # Store all visible dialogs
    group = pygame.sprite.Group()
        
    # Create a dialog with optional initialization
    def __init__(self, body, header = "", target = None, style="dialog", action = None):
        Target.__init__(self, vgroup = Dialog.group)
        
        # This definitions are used for automatic content updating
        self.__target = None
        self.watch(('render', 'target'), self)
        
        # Initialize style for dialog
        self.style = Style(style)
        
        # Set the anchor system
        self.action = action; self.anchors = []
        if action is not None:
            assert callable(action), "Invalid dialog action callback"
        
        # Initialize first properties
        self.body = body; self.header = header; self.target = target
        self.render(); self.relocate()
        
        
    # Modify the body text of a dialog box
    def get_body(self): return self.__body['text']
    
    def set_body(self, body): 
        
        # Set up some information before doing any rendering
        style = self.style; font = style.body_font
        if isinstance(body, basestring): body = body.splitlines()
        width = max(font.size(i)[0] for i in body)
        height = font.get_linesize()
        
        # Create the destination surface
        surf = pygame.Surface((width, (height + 3)* len(body)))
        surf.fill(style.bg)
        self.anchors = []
        
        # Render each line of text to the surface
        offset = 0; spacing = style.spacing
        for i, text in enumerate(body):
            if text.strip() == '-': 
                spacing = int(spacing * 1.3); offset += height // 2
                continue
            if text.startswith('*'):
                text = text[1:].strip()
                line = font.render(text, True, style.anchor_fg, style.bg)
                if self.action:
                    rect = Rect(0, offset+spacing, width, height)
                    self.anchors.append((rect, text))
            else:
                line = font.render(text, True, style.body_fg, style.bg)
            surf.blit(line, (0, offset))
            offset += height + spacing
        
        # Save the surface and flag rendering
        self.__body = {'text':body, 'img':surf}
        self.touch('render')
        
    body = property(get_body, set_body, None)
    
    # Modify the header of a dialog box
    def get_header(self): return self.__header['text']
    
    def set_header(self, header):
        
        # If the header is None, remove the header
        if header: 
            
            # Get style info and render header
            style = self.style; font = style.header_font
            img = font.render(header, True, style.header_fg, style.bg)
            header = {'img':img, 'text':header}
        
        # Save modified data and re-trigger rendering
        self.__header = header 
        self.touch('render')
        
    def del_header(self): self.header = None
        
    header = property(get_header, set_header, del_header)
        
    # Modify the target that the dialog will point to
    def get_target(self): return self.__target 
    def set_target(self, target): 
        
        # Stop watching for previous target if any
        if self.__target: 
            self.__target.unwatch('face', self)
                
        # Set the new target and watch
        if target: 
            target.watch('face', self)
            self.notify('face', target.face)
            
        self.notify('target', bool(target))
        self.__target = target
        
    def del_target(self): self.target = None
        
    target = property(get_target, set_target, del_target)   
        
    # Update multiple dialog contents at once.
    def refresh(self, **kwargs):
        if 'render' in kwargs: self.render()
        if 'face' in kwargs: self.relocate()       
        if 'target' in kwargs: 
            if kwargs['target']: self.relocate()
            else: self.unlocate() 
    
    # Render the Dialog box
    def render(self):
        
        # Calculate the balloon height
        height = self.__body['img'].get_height()
        radius = self.style.border
        if self.__header: 
            height += self.__header['img'].get_height() + radius // 2
        
        # Calculate the balloon width
        width = self.__body['img'].get_width()
        if self.__header: 
            width = max(width, self.__header['img'].get_width()) 
                            
        # Render the balloon
        balloon = shape.balloon(width, height, radius, self.style.bg)
        
        # Paste the contents to the balloon
        dest = Rect((radius, radius), (0,0))
        if self.__header: 
            balloon.blit(self.__header['img'], dest)
            dest.top += self.__header['img'].get_height() + radius
        balloon.blit(self.__body['img'], dest)
        
        self.image = balloon
        
        # Fix the offsets for any existing anchors
        for i, (rect, text) in enumerate(self.anchors):
            rect.top += radius; rect.left += radius
            if self.__header: 
                rect.top += self.__header['img'].get_height() + radius // 2
            self.anchors[i] = (rect, text)
                
    # Position the Dialog Box
    def relocate(self):
        if self.__target: 
            self.align(self.__target.face, 'left,right,bottom,left', 20)
        
    def unlocate(self):
        self.align(self.oldrect, 'bottom,top,left,right', 20)
        
    # Hook for the action-dialog system
    def click(self, pos):
        if self.action:
            pos = rectify(pos)
            for rect, text in self.anchors:
                print rect
                if rect.contains(pos): break
            else: return
            self.action(text)
        
        


        
